﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Threading;
using System.ComponentModel;

namespace System.Windows.Controls
{
    /// <summary>
    /// Implements a custom <see cref="System.Windows.Controls.ItemsControl"/> which arranges its items based on relevancy on the vertical axis and by
    /// a configurable property on the horizontal axis.
    /// </summary>
    public class TileView : System.Windows.Controls.SelectionItemsControl
    {
        /// <summary>
        /// Instantiates a <see cref="TileView"/> instance.
        /// </summary>
        public TileView()
        {
            this.DefaultStyleKey = typeof(TileView);
            this.UseItemAnimation = true;

            animTimer = new Timer(new TimerCallback(this.OnAnimateItemLoop));
            animTimer.Change(Timeout.Infinite, Timeout.Infinite);
        }

        /// <summary>
        /// Identifies the <see cref="UseItemAnimation"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty UseItemAnimationProperty = RegisterDependencyProperty(typeof(TileView), "UseItemAnimation", typeof(bool));

        /// <summary>
        /// Identifies the <see cref="TileWidth"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty TileWidthProperty = RegisterDependencyProperty(typeof(TileView), "TileWidth", typeof(double));

        /// <summary>
        /// Identifies the <see cref="TileHeight"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty TileHeightProperty = RegisterDependencyProperty(typeof(TileView), "TileHeight", typeof(double));

        /// <summary>
        /// Identifies the <see cref="TileSpacing"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty TileSpacingProperty = RegisterDependencyProperty(typeof(TileView), "TileSpacing", typeof(double));

        private ScrollViewer    viewer;
        private TileViewItems   _Items = new TileViewItems();
        private TilePanel       tilePanel;
        private Timer           animTimer;
        private int             animIndex = 0;

        /// <summary>
        /// A handler for dispatching dependency property changes.
        /// </summary>
        /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> event argument</param>
        protected override void OnDependencyPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            if (e.Property == UseItemAnimationProperty)
            {
                this.UseItemAnimation = (bool)e.NewValue;
            }
            else if (e.Property == TileWidthProperty)
            {
                this.TileWidth = (double)e.NewValue;
            }
            else if (e.Property == TileHeightProperty)
            {
                this.TileHeight = (double)e.NewValue;
            }
            else if (e.Property == TileSpacingProperty)
            {
                this.TileSpacing = (double)e.NewValue;
            }
            else
            {
                base.OnDependencyPropertyChanged(e);
            }
        }

        /// <summary>
        /// Gets/Sets a value indicating whether the control will animate items when they are created during a data binding operation.
        /// </summary>
        public bool UseItemAnimation
        {
            get { return (bool)GetValue(UseItemAnimationProperty); }
            set { SetValue(UseItemAnimationProperty, value); }
        }

        /// <summary>
        /// Gets/Sets a value indicating the width of the <see cref="TileViewItem"/> instances.
        /// </summary>
        public double TileWidth
        {
            get { return (double)GetValue(TileWidthProperty); }
            set { SetValue(TileWidthProperty, value); }
        }

        /// <summary>
        /// Gets/Sets a value indicating the height of the <see cref="TileViewItem"/> instances.
        /// </summary>
        public double TileHeight
        {
            get { return (double)GetValue(TileHeightProperty); }
            set { SetValue(TileHeightProperty, value); }
        }

        /// <summary>
        /// Gets/Sets a value indicating the spacing between the <see cref="TileViewItem"/> instances.
        /// </summary>
        public double TileSpacing
        {
            get { return (double)GetValue(TileSpacingProperty); }
            set { SetValue(TileSpacingProperty, value); }
        }

        /// <summary>
        /// Gets the size of the scrolling viewport
        /// </summary>
        public Size ViewportSize
        {
            get
            {
                if (viewer != null)
                {
                    return new Size(Math.Max(0, viewer.ActualWidth - 48), Math.Max(0, viewer.ActualHeight - 48));
                }

                return Size.Empty;
            }
        }

        /// <summary>
        /// Examines an item to determine if it is the appropriate item container for this control
        /// </summary>
        /// <param name="item">The item to examine</param>
        /// <returns>A boolean value indicating that the item is the correct container (true) or not (false).</returns>
        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return (item is TileViewItem);
        }

        /// <summary>
        /// Creates a container item for this control
        /// </summary>
        /// <returns>A <see cref="System.Windows.DependencyObject"/> instance</returns>
        protected override DependencyObject GetContainerForItemOverride()
        {
            TileViewItem item = new TileViewItem();
            item.Opacity = (this.UseItemAnimation ? 0 : 1);
            return item;
        }

        /// <summary>
        /// Performs removal record-keeping for an item container used by this control
        /// </summary>
        /// <param name="element">The visual element being removed</param>
        /// <param name="item">The data item associated with the visual element</param>
        protected override void ClearContainerForItemOverride(DependencyObject element, object item)
        {
            base.ClearContainerForItemOverride(element, item);

            if (element is TileViewItem)
            {
                TileViewItem tileItem = (TileViewItem)element;
                if (_Items.Contains(tileItem)) _Items.Remove(tileItem);
            }
        }

        /// <summary>
        /// Prepares an item element for insertion into this control
        /// </summary>
        /// <param name="element">The visual element to being inserted</param>
        /// <param name="item">The data item associated with the visual element</param>
        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            base.PrepareContainerForItemOverride(element, item);

            if (element is TileViewItem)
            {
                TileViewItem tileItem = (TileViewItem)element;
                if (!_Items.Contains(tileItem)) _Items.Add(tileItem);

                tileItem.Opacity = (this.UseItemAnimation ? 0 : 1);
            }
        }

        internal void AnimateItems()
        {
            animIndex = 0;
            animTimer.Change(0, 50);
        }

        private void OnAnimateItemLoop(object context)
        {
            if (!DesignerProperties.IsInDesignTool)
            {
                Dispatcher.BeginInvoke(new Action(AdvanceAnimation));
            }
        }

        private void AdvanceAnimation()
        {
            if (animIndex >= 0 && animIndex < _Items.Count)
            {
                _Items[animIndex].StartAnimation();
                animIndex++;
            }
            else
            {
                animTimer.Change(Timeout.Infinite, Timeout.Infinite);
            }
        }

        /// <summary>
        /// (Override) Processes the control template for this <see cref="BandCloudView"/> instance.
        /// </summary>
        public override void OnApplyTemplate()
        {
            viewer = VerifyTemplateChild<ScrollViewer>("ScrollViewer");
            base.OnApplyTemplate();
        }

        /// <summary>
        /// (Override) Performs custom steps during an Arrange pass.
        /// </summary>
        /// <param name="finalSize">The size of the control</param>
        /// <returns>The arranged size of the control</returns>
        protected override Size ArrangeOverride(Size finalSize)
        {
            VerifyPanel();
            return base.ArrangeOverride(finalSize);
        }

        private void VerifyPanel()
        {
            if (tilePanel == null)
            {
                if (viewer != null && viewer.Content != null)
                {
                    ItemsPresenter presenter = (ItemsPresenter)viewer.Content;
                    int count = VisualTreeHelper.GetChildrenCount(presenter);
                    for (int index = 0; index < count; index++)
                    {
                        DependencyObject panelObj = VisualTreeHelper.GetChild(presenter, index);
                        if (panelObj is TilePanel)
                        {
                            //Get a reference to the Cloud Panel for interaction relative to
                            //magnification and arranger selection
                            tilePanel = (TilePanel)panelObj;
                            tilePanel.ParentView = this;
                            tilePanel.TileWidth = this.TileWidth;
                            tilePanel.TileHeight = this.TileHeight;
                            tilePanel.TileSpacing = this.TileSpacing;
                            break;
                        }
                    }
                }
            }
        }
    }

    /// <summary>
    /// Implements an <see cref="System.Windows.Controls.ItemControl"/> derived item class for use in a <see cref="TileView"/> instance.
    /// </summary>
    public class TileViewItem : System.Windows.Controls.ItemControl
    {
        /// <summary>
        /// Instantiates a <see cref="TileViewItem"/> instance.
        /// </summary>
        public TileViewItem()
        {
            this.DefaultStyleKey = typeof(TileViewItem);
            this.HorizontalContentAlignment = HorizontalAlignment.Center;

            translate = new TranslateTransform();
            this.RenderTransform = translate;

            //Center Reveals
            revealStory = Animation.CreateStoryboard(TimeSpan.FromSeconds(0.25));
            moveX = Animation.CreateAnimation(revealStory, translate, "X", 0, 0);
            revealStory.Children.Add(moveX);
            moveY = Animation.CreateAnimation(revealStory, translate, "Y", 0, 0);
            revealStory.Children.Add(moveY);
            fadeAnim = Animation.CreateAnimation(revealStory, this, "Opacity", 0, 0);
            revealStory.Children.Add(fadeAnim);
            this.Resources.Add(revealStory);
        }

        private Storyboard          revealStory;
        private DoubleAnimation     moveX;
        private DoubleAnimation     moveY;
        private DoubleAnimation     fadeAnim;
        private TranslateTransform  translate;

        internal void SetAnimationOrigin(double x, double y, double opacity)
        {
            moveX.From = x;
            moveX.To = 0;
            moveY.From = y;
            moveY.To = 0;
            fadeAnim.From = opacity;
            fadeAnim.To = 1;
        }

        internal void StartAnimation()
        {
            revealStory.Begin();
        }
    }

    /// <summary>
    /// Implements a <see cref="System.Collections.Generic.List&lt;T&gt;"/> collection of <see cref="TileViewItem"/> instances.
    /// </summary>
    public class TileViewItems : List<TileViewItem>
    {

    }

    /// <summary>
    /// Implements a custom <see cref="System.Windows.Controls.Panel"/> for performing the custom layout for a <see cref="TileView"/> instance.
    /// </summary>
    public class TilePanel : System.Windows.Controls.Panel
    {
        /// <summary>
        /// Instantiates a <see cref="TilePanel"/> instance.
        /// </summary>
        public TilePanel()
        {
            this.TileWidth = this.TileHeight = 96;
            this.TileSpacing = 8;
        }

        private TileView    _ParentView;

        /// <summary>
        /// Gets the parent <see cref="TileView"/> instance for this <see cref="TilePanel"/> instance.
        /// </summary>
        public TileView ParentView
        {
            get { return _ParentView; }
            internal set { _ParentView = value; }
        }

        /// <summary>
        /// Identifies the <see cref="TileWidth"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty TileWidthProperty = DependencyProperty.Register("TileWidth", typeof(double), typeof(TilePanel), new PropertyMetadata(OnTileWidthPropertyChanged));

        /// <summary>
        /// Identifies the <see cref="TileHeight"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty TileHeightProperty = DependencyProperty.Register("TileHeight", typeof(double), typeof(TilePanel), new PropertyMetadata(OnTileHeightPropertyChanged));

        /// <summary>
        /// Identifies the <see cref="TileSpacing"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty TileSpacingProperty = DependencyProperty.Register("TileSpacing", typeof(double), typeof(TilePanel), new PropertyMetadata(OnTileSpacingPropertyChanged));

        private static void OnTileWidthPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (obj is TilePanel) ((TilePanel)obj).TileWidth = (double)e.NewValue;
        }

        private static void OnTileHeightPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (obj is TilePanel) ((TilePanel)obj).TileHeight = (double)e.NewValue;
        }

        private static void OnTileSpacingPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (obj is TilePanel) ((TilePanel)obj).TileSpacing = (double)e.NewValue;
        }

        /// <summary>
        /// Gets/Sets a value which determines the width of a single tile in this <see cref="TilePanel"/> instance.
        /// </summary>
        public double TileWidth
        {
            get { return (double)GetValue(TileWidthProperty); }
            set { SetValue(TileWidthProperty, value); }
        }

        /// <summary>
        /// Gets/Sets a value which determines the height of a single tile in this <see cref="TilePanel"/> instance.
        /// </summary>
        public double TileHeight
        {
            get { return (double)GetValue(TileHeightProperty); }
            set { SetValue(TileHeightProperty, value); }
        }

        /// <summary>
        /// Gets/Sets a value which determines the spacing between items in this <see cref="TilePanel"/> instance.
        /// </summary>
        public double TileSpacing
        {
            get { return (double)GetValue(TileSpacingProperty); }
            set { SetValue(TileSpacingProperty, value); }
        }

        /// <summary>
        /// (Override) Provides custom measurement functions for this <see cref="TilePanel"/>.
        /// </summary>
        /// <param name="availableSize">The available size for the layout</param>
        /// <returns>The actual size for the layout</returns>
        protected override Size MeasureOverride(Size availableSize)
        {
            Point origin = new Point();

            double width = this.TileWidth;
            double height = this.TileHeight;
            double spacing = this.TileSpacing;

            Size size = new Size();

            for(int index = 0; index < this.Children.Count; index++)
            {
                UIElement element = this.Children[index];

                if (element is FrameworkElement)
                {
                    origin.X += width + spacing;

                    if (index > 0)
                    {
                        if (origin.X + width >= availableSize.Width)
                        {
                            origin.X = 0;
                            origin.Y += height + spacing;
                        }
                    }

                    if (origin.X + width >= size.Width) size.Width = origin.X + width;
                    if (origin.Y + height >= size.Height) size.Height = origin.Y + height;
                }
            }

            return size;
        }

        /// <summary>
        /// (Override) Provides custom arrange functions for the <see cref="TilePanel"/> instance.
        /// </summary>
        /// <param name="finalSize">The layout size for the control</param>
        /// <returns>The actual size of the layout</returns>
        protected override Size ArrangeOverride(Size finalSize)
        {
            Point origin = new Point();
            Point ptCenter = new Point(this.DesiredSize.Width / 2, this.DesiredSize.Height / 2);
            Size size = new Size();

            for(int index = 0; index < this.Children.Count; index++)
            {
                UIElement element = this.Children[index];

                if (element is FrameworkElement)
                {
                    FrameworkElement frameElement = (FrameworkElement)element;
                    double width = this.TileWidth;
                    double height = this.TileHeight;
                    double spacing = this.TileSpacing;

                    element.Arrange(new Rect(origin.X, origin.Y, width, height));

                    if (element is TileViewItem)
                    {
                        TileViewItem viewItem = (TileViewItem)element;
                        viewItem.SetAnimationOrigin(ptCenter.X - origin.X, ptCenter.Y - origin.Y, 0);
                    }

                    origin.X += width + spacing;

                    if (index > 0)
                    {
                        if (origin.X + width >= finalSize.Width)
                        {
                            origin.X = 0;
                            origin.Y += height + spacing;
                        }
                    }

                    if (origin.X + width > size.Width) size.Width = origin.X + width;
                    if (origin.Y + height > size.Height) size.Height = origin.Y + height;
                }
            }

            //Fire Animations
            if (this.ParentView != null)
            {
                this.ParentView.AnimateItems();
            }

            return size;
        }
    }
}
